// SUGARSCAPE MODEL FOR SWARM
// Based on Nelson Minar's 1996-1997 model; modified by 
// Andre Manoel (andremanoel@gmail.com) with research  
// purposes. See file README for more information.

// Here we implement the class SugarSpace
#import "SugarSpace.h"
#import "SugarAgent.h"
#import <simtools.h> // InFile
#import <misc.h> // strdup

@implementation SugarSpace

//// The creation of the SugarSpace world - here we create a lot of objects
////   to represent the world space
- createEnd
{
  [super createEnd];

  // Check that we have a reasonable size
  if (xsize <= 0 || ysize <= 0)
    [InvalidCombination raiseEvent:
     "SugarSpace was created with an invalid size\n"];
  // and a datafile
  if (maxSugarDataFile == NULL || maxSpiceDataFile == NULL)
    [InvalidCombination raiseEvent:
     "SugarSpace was created without a data file for max sugar.\n"];

  // Create the array to represent the sugar itself
  sugar = [Discrete2d createBegin: [self getZone]];
  [sugar setSizeX: xsize Y: ysize];
  sugar = [sugar createEnd];

  [self setSugarGrowRate: 1];

  // Now create an object to represent the maximum sugar values
  maxSugar = [Discrete2d createBegin: [self getZone]];
  [maxSugar setSizeX: xsize Y: ysize];
  maxSugar = [maxSugar createEnd];
 
  // The space library's setDiscrete2d: toFile: method returns the max value
  globalMaxSugar = [maxSugar setDiscrete2d: maxSugar toFile: maxSugarDataFile];

  // Start the sugar out at maximum
  [maxSugar copyDiscrete2d: maxSugar toDiscrete2d: sugar];

  // Create the array to represent spice
  spice = [Discrete2d createBegin: [self getZone]];
  [spice setSizeX: xsize Y: ysize];
  spice = [spice createEnd];

  [self setSpiceGrowRate: 1];

  // And the maximum spice values
  maxSpice = [Discrete2d createBegin: [self getZone]];
  [maxSpice setSizeX: xsize Y: ysize];
  maxSpice = [maxSpice createEnd];

  if (activSpice == 1) {
    globalMaxSpice = 
      [maxSpice setDiscrete2d: maxSpice toFile: maxSpiceDataFile];
    [maxSpice copyDiscrete2d: maxSpice toDiscrete2d: spice];
  }
  else {
    [spice fillWithValue: 0];
    [self setSpiceGrowRate: 0];
  }  

  // Create an array to represent sugar and spice
  values = [Discrete2d createBegin: [self getZone]];
  [values setSizeX: xsize Y: ysize];
  values = [values createEnd];

  // Create the array to represent pollution
  pollution = [Discrete2d createBegin: [self getZone]];
  [pollution setSizeX: xsize Y: ysize];
  pollution = [pollution createEnd];
  [pollution fillWithValue: 0];

  // Finally, set up the grid used to represent agent position
  agentGrid = [Grid2d createBegin: [self getZone]];
  [agentGrid setSizeX: xsize Y: ysize];
  agentGrid = [agentGrid createEnd];

  [self setTime: 0];

  return self;
}

//// Update the sugar - this is the dynamics of the sugarspace world. It
////   is the rule G_alpha explained on page 26. 
- updateSugar
{
  unsigned int x, y;
  float actualSugarGrowRate;
  int plagueMultiplier;

  // Plague check
  if (time > plagueEach && time % plagueEach <= plagueFor) 
    plagueMultiplier = 0;
  else plagueMultiplier = 1;

  // Loop through the world
  for (x = 0; x < xsize; x++) {
    for (y = 0; y < ysize; y++) {

      // Verify if it's summer or winter
      if ((time % (2*winterDuration)) / winterDuration < 1) {
        if (y <= ysize/2) actualSugarGrowRate = sugarGrowRate; 
        else actualSugarGrowRate = winterGrowRate; 
      }
      else {
        if (y <= ysize/2) actualSugarGrowRate = winterGrowRate; 
        else actualSugarGrowRate = sugarGrowRate; 
      }
      actualSugarGrowRate *= plagueMultiplier;
	  
      int sugarHere = [sugar getValueAtX: x Y: y];
      int maxSugarHere = [maxSugar getValueAtX: x Y: y];

      if (sugarHere + actualSugarGrowRate < maxSugarHere) 
        sugarHere = sugarHere + actualSugarGrowRate;
      else 
        sugarHere = maxSugarHere;  
      [sugar putValue: sugarHere atX: x Y: y]; 
    }
  }
 
  return self;
}

- updateSpice
{
  unsigned int x, y;

  // Loop through the world
  if (activSpice == 1) {
    for (x = 0; x < xsize; x++) {
      for (y = 0; y < ysize; y++) {
	  
        int spiceHere = [spice getValueAtX: x Y: y];
        int maxSpiceHere = [maxSpice getValueAtX: x Y: y];
	  
        if (spiceHere + spiceGrowRate < maxSpiceHere) 
          spiceHere = spiceHere + spiceGrowRate;
        else 
          spiceHere = maxSpiceHere;  
        [spice putValue: spiceHere atX: x Y: y]; 
      }
    }
  }
 
  return self;
}

//// Handle manipulating the sugar and spice in the world

// Read how much sugar is at a particular spot
- (SugarValue)getSugarAtX: (int)x Y: (int)y
{
  x = [self xnorm: x];
  y = [self ynorm: y];
  return (SugarValue) [sugar getValueAtX: x Y: y];
}

// Take sugar from a spot in the world
- (SugarValue)takeSugarAtX: (int)x Y: (int)y
{
  SugarValue sugarHere;
  
  x = [self xnorm: x];
  y = [self ynorm: y];
  sugarHere = [sugar getValueAtX: x Y: y];
  [sugar putValue: 0 atX: x Y: y];

  return sugarHere;
}

- (SugarValue)takeSugar: (float)s AtX: (int)x Y: (int)y 
{
  SugarValue sugarHere;
  
  x = [self xnorm: x];
  y = [self ynorm: y];
  sugarHere = [sugar getValueAtX: x Y: y];
  if (s > sugarHere) s = sugarHere;
  [sugar putValue: sugarHere-s atX: x Y: y];
  
  return s;
}

// Read how much spice is at a particular spot
- (SpiceValue)getSpiceAtX: (int)x Y: (int)y
{
  SpiceValue value;
  x = [self xnorm: x];
  y = [self ynorm: y];

  if (activSpice == 1) value = [spice getValueAtX: x Y: y];
  else value = 0;
  return value;
}

// Take spice from a spot in the world
- (SpiceValue)takeSpiceAtX: (int)x Y: (int)y
{
  SpiceValue spiceHere;
  
  x = [self xnorm: x];
  y = [self ynorm: y];
  
  if (activSpice == 1) {
    spiceHere = [spice getValueAtX: x Y: y];
    [spice putValue: 0 atX: x Y: y];
  }
  else spiceHere = 0;

  return spiceHere;
}

- (SpiceValue)takeSpice: (float)s AtX: (int)x Y: (int)y 
{
  SpiceValue spiceHere;
  
  x = [self xnorm: x];
  y = [self ynorm: y];
 
  if (activSpice == 1) {
    spiceHere = [spice getValueAtX: x Y: y];
    [spice putValue: spiceHere-s atX: x Y: y];
  }
  else spiceHere = 0;
 
  return s;
}

// Methods to manage time counter
- setTime: (int)t
{
  time = t;
  return self;
}

- (int)getTime
{
  return time;
}

- updateTime
{
  time++;
  return self;
}

// Implement pollution on the Sugarscape
- setPollutionS: (float)s M: (float)m diffEach: (int)turns
{
  pollutionS = s;
  pollutionM = m;
  turnsToDiffuse = turns;
  return self;
}

- setPollutStart: (int)ps pollutEnd: (int)pe diffStart: (int)ds
{
  polStart = ps;
  polEnd = pe;
  diffStart = ds;
  return self;
}

- (float)getPollutAtX: (int)x Y: (int)y
{
  x = [self xnorm: x];
  y = [self ynorm: y];

  float pol = [pollution getValueAtX: x Y: y];
  return pol;
}

- addPollutionForS: (float)s forM: (float)m atX: (int)x Y: (int)y
{
  if (time >= polStart && time < polEnd) {
    float pollutionHere = [pollution getValueAtX: x Y: y];
    pollutionHere += pollutionS*s + pollutionM*m;
    [pollution putValue: pollutionHere atX: x Y: y];
  }

  return self;
}
                
- diffusePollution
{
  unsigned int x, y;
  float newPollution[xsize][ysize];

  if (turnsToDiffuse != 0) {
    if (time >= diffStart && time % turnsToDiffuse == 0) {

    // Loop through the world
      for (x = 0; x < xsize; x++) {
        for (y = 0; y < ysize; y++) {
          newPollution[x][y] = [pollution getValueAtX: [self xnorm:x+1] Y: y];
          newPollution[x][y] += [pollution getValueAtX: x Y: [self ynorm:y+1]];
          newPollution[x][y] += [pollution getValueAtX: [self xnorm:x-1] Y: y];
          newPollution[x][y] += [pollution getValueAtX: x Y: [self ynorm:y-1]];
        }
      }

      for (x = 0; x < xsize; x++) {
        for (y = 0; y < ysize; y++) {
          [pollution putValue: newPollution[x][y]/4 atX: x Y: y];
        }
      }
    }
  }

  return self;
}

// Manage plagues on the sugarscape
- setPlagueEach: (int)e forLongAs: (int)f
{  
  plagueEach = e;
  plagueFor = f;
  return self;
}

//// Code to manage agent positions in the Sugarscape. Two technical points:
////   All coordinates are normalized first. This enforces wraparound world
////   No effort is made here to make sure two agents don't collide

// Return the agent at a particular spot.
- (SugarAgent *)getAgentAtX: (int)x Y: (int)y
{
  return [agentGrid getObjectAtX: [self xnorm: x] Y: [self ynorm: y]];
}

// Add a new agent to the world. Note, this code updates the agent's
//   own idea of where it is. We're violating OO encapsulation here,
//   but that's ok
- addAgent: (SugarAgent *)agent atX: (int)x Y: (int)y
{
  x = [self xnorm: x];
  y = [self ynorm: y];
  agent->x = x;
  agent->y = y;
  [agentGrid putObject: agent atX: x Y: y];

  return self;
}

// Remove an agent from the world
- removeAgent: (SugarAgent *)agent
{
  int x, y;

  x = [self xnorm: agent->x];
  y = [self ynorm: agent->y];
  if ([self getAgentAtX: x Y: y] == agent)
    [agentGrid putObject: nil atX: x Y: y];

  return self;
}

// Move the agent
- moveAgent: (SugarAgent *)agent toX: (int)x Y: (int)y
{
  [self removeAgent: agent];
  [self addAgent: agent atX: x Y: y];

  return self;
}

//// The code below here is not very interesting - it has to do with
//// the technical details of managing the data in the model, not the
//// modelling itself. You can safely ignore it until you want to know
//// the details of how the program works

// Handle the size of the world
- setSizeX: (int)x Y: (int)y
{
  xsize = x;
  ysize = y;

  return self;
}

- (int)getSizeX
{
  return xsize;
}

- (int)getSizeY
{
  return ysize;
}

- (SugarValue)getGlobalMaxSugar
{
  return globalMaxSugar;
}

- (SpiceValue)getGlobalMaxSpice
{
  return globalMaxSpice;
}

- (id <Grid2d>)getAgentGrid
{
  return agentGrid;
}

- (id <Discrete2d>)getValues
{ 
  return values;
}

- updateValues
{
  int i, j;
  int sugarV, spiceV;

  if (activSpice == 1) {
    for (i = 0; i < xsize; i++) {
      for (j = 0; j < ysize; j++) {
        sugarV = [sugar getValueAtX: i Y: j];
        spiceV = [spice getValueAtX: i Y: j];
        if (sugarV >= spiceV) [values putValue: sugarV atX: i Y: j];
        else {
          [values putValue: spiceV+50 atX: i Y: j];
        }
      }
    }
  }
  else [sugar copyDiscrete2d: sugar toDiscrete2d: values];
    

  return self;
}

// Accessor methods for sugar and spice growth rates
- setSugarGrowRate: (int)a;
{
  sugarGrowRate = a;
  return self;
}

- (int)getSugarGrowRate
{
  return sugarGrowRate;
}

- setSeasonsOf: (int)t WithGrowRate: (float)g
{
  winterDuration = t;
  winterGrowRate = g;
  return self;
}

- setSpiceGrowRate: (int)a;
{
  spiceGrowRate = a;
  return self;
}

- (int)getSpiceGrowRate
{
  return spiceGrowRate;
} 
 
// Copy the filenames into our object
- setMaxSugarDataFile: (const char *)s
{
  maxSugarDataFile = strdup (s);
  return self;
}

- setMaxSpiceDataFile: (const char *)s
{
  maxSpiceDataFile = strdup (s);
  if (strcmp(s, "none") != 0) activSpice = 1;
  else activSpice = 0;  

  return self;
}

// Normalize coordinates
- (int)xnorm: (int)x
{
  if (x < 0)					  // negative?
    return (x + xsize * 128) % xsize;		  // make positive, round
  else if (x >= (int)xsize)				  // too big? round
    return x % xsize;
  else
    return x;					  // just right
}

- (int)ynorm: (int)y
{
  if (y < 0)
    return (y + ysize * 128) % ysize;
  else if (y >= (int)ysize)
    return y % ysize;
  else
    return y;
}

@end
